package com.spring.component.utils;

import java.io.*;
import java.net.Socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName: SocketClient
 * @Description:
 * @author: liushihua
 * @date: 2018/12/20
 **/
public class SocketClient {

  private static final transient Logger log = LoggerFactory.getLogger(SocketClient.class);

  private String host = "localhost";
  private int port = 0;
  /** 读取数据时阻塞链路的超时时间，一次读取数据超过这个时间会抛出异常 */
  private int timeOut = 0;
  private Socket socket = null;
  private InputStream in = null;
  private OutputStream out = null;
  private BufferedReader reader = null;
  private PrintWriter writer = null;
  private byte[] buffer = new byte[1024];
  private String charset = "UTF-8";

  public SocketClient(String host, int port) {
    this.host = host;
    this.port = port;
  }

  public SocketClient(String host, int port, String charset) {
    this.host = host;
    this.port = port;
    this.charset = charset;
  }

  public SocketClient(String host, int port, int timeOut) {
    this.host = host;
    this.port = port;
    this.timeOut = timeOut;
  }

  /**
   * 取得主机地址
   * 
   * @return
   */
  public String getHost() {
    return host;
  }

  /**
   * 取得主机端口号
   * 
   * @return
   */
  public int getPort() {
    return port;
  }

  /**
   * 取得超时时长(ms)
   * 
   * @return
   */
  public int getTimeOut() {
    return timeOut;
  }

  /**
   * 设置字符编码
   * 
   * @param charset
   */
  public void setCharset(String charset) {
    this.charset = charset;
  }

  private void initStream() throws IOException {
    if (null != socket) {
      if (timeOut > 0) {
        socket.setSoTimeout(timeOut);
      }
      in = socket.getInputStream();
      out = socket.getOutputStream();
      reader = new BufferedReader(new InputStreamReader(in, charset));
      writer = new PrintWriter(out);
    }
  }

  /**
   * 建立socket连接
   * 
   * @throws IOException
   */
  public void open() throws IOException {
    socket = new Socket(host, port);
    log.info("连接服务端[{}:{}]成功！！！", host, port);
    initStream();
  }

  /**
   * 阻断连接服务器，每隔3s重新连接
   * 
   * @throws Exception
   */
  public void openBlock() throws Exception {
    while (true) {
      Thread.sleep(3000);
      try {
        socket = new Socket(host, port);
      } catch (IOException e) {
        log.info("连接服务端[{}:{}]失败，3s后重连", host, port);
      }
      break;
    }
    log.info("连接服务端[{}:{}]成功！！！", host, port);
    initStream();
  }

  /**
   * 关闭socket连接
   * 
   * @throws IOException
   */
  public void close() throws IOException {
    if (null != socket) {
      in.close();
      out.close();
      socket.close();
    }
  }

  /**
   * 检测Socket连接是否正常
   * 
   * @return
   */
  public boolean isSocketConnect() {
    if (socket == null || socket.isClosed() || !socket.isConnected()) {
      return false;
    }
    return true;
  }

  /**
   * 发送心跳检测服务端是否断开
   *
   * @return
   */
  public boolean isDisconnect() {
    try {
      // 发送心跳
      socket.sendUrgentData(0xFF);
      return false;
    } catch (Exception e) {
      return true;
    }
  }

  /**
   * 发送字节流
   * 
   * @param data
   * @throws Exception
   */
  public void send(byte[] data) throws IOException {
    if (data == null || data.length == 0) {
      throw new IOException("发送的数据为空！");
    }
    out.write(data);
    out.flush();
    log.debug("发送报文：{}", new String(data, charset));
  }

  /**
   * 发送指定长度字节流
   * 
   * @param data 待发送数据
   * @param offset 待发送数据偏移
   * @param length 待发送数据最大长度
   * @throws Exception
   */
  public void send(byte[] data, int offset, int length) throws IOException {
    if (data == null || data.length == 0) {
      throw new IOException("发送的数据为空！");
    }
    if (data.length < offset + length) {
      throw new IOException("指定的数组长度越界");
    }
    out.write(data, offset, length);
    out.flush();
  }

  /**
   * 发送文本行
   * 
   * @param data
   * @throws Exception
   */
  public void writeLine(String data) throws IOException {
    if (data == null || data.length() == 0) {
      throw new IOException("发送的数据为空！");
    }
    writer.write(data);
    writer.flush();
    log.debug("发送报文：{}", data);
  }

  /**
   * 接收数据
   *
   * @return
   * @throws IOException
   */
  public byte[] receive() throws IOException {
    int size = in.read(buffer);
    if (size < 0) {
      throw new IOException("Socket reciver异常！");
    }
    byte[] data = new byte[size];
    System.arraycopy(buffer, 0, data, 0, size);
    log.debug("接收的报文：{}", new String(data, charset));
    return data;
  }

  /**
   * 接收数据
   * 
   * @param data 接收数据存放的对象
   * @param offset 接收数据存放偏移
   * @param length 接收数据存放长度
   * @return int 接收到的数据长度
   * @throws IOException
   */
  public int reveive(byte[] data, int offset, int length) throws IOException {
    if (data == null) {
      throw new IOException("指定的缓冲区为空！");
    }
    if (data.length < offset + length) {
      throw new IOException("指定的缓冲区长度越界！");
    }
    int size = 0;
    if (in.available() > 0) {
      size = in.read(data, offset, length);
    }
    return size;
  }

  /**
   * 接收数据，发生tcp拆包后等待接收完整数据
   * 
   * @param data
   * @param offset
   * @param length
   * @return
   * @throws IOException
   */
  public int reveiveFull(byte[] data, int offset, int length) throws IOException {
    if (data == null) {
      throw new IOException("指定的缓冲区为空！");
    }
    if (data.length < offset + length) {
      throw new IOException("指定的缓冲区长度越界！");
    }
    if (in.available() < 0) {
      return 0;
    }
    int size = in.read(data, offset, length);
    if (size <= 0) {
      return 0;
    }
    // 解决tcp拆包
    while (size < length) {
      log.debug("发生TCP拆包，要接收size：" + length + "， 已接收size：" + size);
      int temp = in.read(data, size, length - size);
      size += temp;
    }
    return size;
  }

  /**
   * 接收文本行
   *
   * @throws IOException
   * @return byte[]
   */
  public String readLine() throws IOException {
    return reader.readLine();
  }

  public static String toStringHex(byte[] bb) {
    StringBuffer sb = new StringBuffer();
    int i = 0;
    for (; i < 12; ++i) {
      String v = Integer.toHexString(bb[i] & 0xFF);
      if (v.length() < 2) {
        sb.append("0").append(v);
      } else {
        sb.append(v);
      }
    }
    int len = bb.length - 2;
    for (; i < len; ++i) {
      sb.append((char) bb[i]);
    }

    for (; i < bb.length; ++i) {
      String v = Integer.toHexString(bb[i] & 0xFF);
      if (v.length() < 2) {
        sb.append("0").append(v);
      } else {
        sb.append(v);
      }
    }

    return sb.toString();
  }
}
